# main_mcp_server.py

import logging
import sys
import math
import random
import requests
import os
import json
import ssl
import time
import threading
from mcp.server.fastmcp import FastMCP
import paho.mqtt.client as mqtt

# --- 基础设置 ---
# 配置日志记录以显示所有工具的消息
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('MainMCPServer')

# 为Windows控制台修复UTF-8编码问题，这对合并后的脚本很有用
if sys.platform == 'win32':
    sys.stderr.reconfigure(encoding='utf-8')
    sys.stdout.reconfigure(encoding='utf-8')

# 创建一个MCP服务器实例来容纳所有工具
# 我们给它一个通用的名字，因为它现在有多种用途
mcp = FastMCP("XiaoZhi-Tools")

# --- 自定义异常 ---
class WeatherServiceError(Exception):
    """天气服务相关的自定义异常。"""
    pass

class PyCarControlError(Exception):
    """pyCar控制相关的自定义异常。"""
    pass

# --- 配置加载 ---
def get_config():
    """加载主配置文件。"""
    script_dir = os.path.dirname(os.path.abspath(__file__))
    config_path = os.path.join(script_dir, 'config.json')
    if not os.path.exists(config_path):
        raise WeatherServiceError("配置文件 'config.json' 未找到。")
    with open(config_path, 'r', encoding='utf-8') as f:
        return json.load(f)

# 在模块级别加载配置以提高效率
try:
    config = get_config()
    API_KEYS = config.get("api_keys", {})
    HEFENG_API_KEY = API_KEYS.get("hefeng_weather")
    HEFENG_API_HOST = API_KEYS.get("hefeng_weather_host")
    PYCAR_MQTT_CONFIG = config.get("pycar_mqtt", {})
except Exception as e:
    logger.error(f"加载或解析配置失败: {e}")
    # 设置为None以表示配置失败
    HEFENG_API_KEY = None
    HEFENG_API_HOST = None
    PYCAR_MQTT_CONFIG = {}


# --- 工具1：计算器 ---
@mcp.tool()
def calculator(python_expression: str) -> dict:
    """
    用于数学计算。此工具会计算一个Python表达式的结果。
    可用的模块: `math` 和 `random`。
    注意: 为安全起见，此工具使用了沙箱化的eval()，仅开放math和random模块。
    """
    try:
        # 使用受限的eval来防止执行任意代码
        # 全局变量被清除，仅显式提供'math'和'random'
        safe_globals = {"__builtins__": None}
        safe_locals = {"math": math, "random": random}
        result = eval(python_expression, safe_globals, safe_locals)
        logger.info(f"计算表达式: {python_expression}，结果: {result}")
        return {"success": True, "result": str(result)} # 确保结果是字符串类型
    except Exception as e:
        logger.error(f"计算表达式 '{python_expression}' 时出错: {e}")
        return {"success": False, "error": str(e)}

# --- 工具2：本地天气服务 ---

# 天气服务的辅助函数
def _get_location_from_ip():
    """根据公网IP获取地理位置。"""
    logger.info("正在根据IP地址获取位置...")
    try:
        response = requests.get('http://ip-api.com/json/', timeout=5)
        response.raise_for_status()
        data = response.json()
        if data.get('status') != 'success':
            raise WeatherServiceError(f"获取位置失败: {data.get('message')}")
        return data
    except requests.exceptions.RequestException as e:
        raise WeatherServiceError(f"获取位置时网络出错: {e}")

def _get_weather_data(location_data):
    """从和风天气API获取天气数据。"""
    city = location_data.get('city', '未知')
    lat = location_data.get('lat')
    lon = location_data.get('lon')
    location_param = f"{lon:.2f},{lat:.2f}"

    logger.info(f"正在使用和风天气为 {city} ({location_param}) 获取天气...")
    try:
        weather_url = f"https://{HEFENG_API_HOST}/v7/weather/now?location={location_param}&key={HEFENG_API_KEY}&lang=zh"
        response = requests.get(weather_url, timeout=5)
        response.raise_for_status()
        data = response.json()
        if data.get("code") != "200":
            raise WeatherServiceError(f"和风天气API错误 (代码: {data.get('code')})。")
        return data
    except requests.exceptions.RequestException as e:
        raise WeatherServiceError(f"获取天气时网络出错: {e}")

def _format_weather_result(city, weather_data):
    """格式化最终的天气结果字符串。"""
    now_weather = weather_data.get('now', {})
    description = now_weather.get('text', 'N/A')
    temp = now_weather.get('temp', 'N/A')
    feels_like = now_weather.get('feelsLike', 'N/A')
    humidity = now_weather.get('humidity', 'N/A')
    return f"您当前位于 {city}。天气：{description}，温度 {temp}°C，体感温度 {feels_like}°C，湿度 {humidity}%。"

@mcp.tool()
def get_local_weather() -> dict:
    """
    根据用户的公网IP地址，获取其当前位置的天气预报。
    """
    try:
        # 步骤1：验证配置
        if not HEFENG_API_KEY or HEFENG_API_KEY == "YOUR_HEFENG_API_KEY_HERE":
            raise WeatherServiceError("和风天气API密钥未在config.json中配置。")
        if not HEFENG_API_HOST or HEFENG_API_HOST == "YOUR_HEFENG_API_HOST_HERE":
            raise WeatherServiceError("和风天气API主机地址未在config.json中配置。")
        
        # 步骤2：获取位置和天气
        location_data = _get_location_from_ip()
        weather_data = _get_weather_data(location_data)
        
        # 步骤3：格式化并返回结果
        city = location_data.get('city', '未知')
        result_text = _format_weather_result(city, weather_data)
        
        logger.info(f"成功获取天气: {result_text}")
        return {"success": True, "result": result_text}

    except (WeatherServiceError, Exception) as e:
        logger.error(f"get_local_weather 发生错误: {e}")
        return {"success": False, "error": str(e)}

# --- 工具3：pyCar小车控制 ---

# MQTT客户端管理类
class PyCarMQTTClient:
    def __init__(self):
        self.client = None
        self.connected = False
        self.last_error = None
        
    def connect(self):
        """连接到pyCar的MQTT服务器"""
        try:
            if not PYCAR_MQTT_CONFIG:
                raise PyCarControlError("pyCar MQTT配置未在config.json中设置")
            
            broker = PYCAR_MQTT_CONFIG.get("broker")
            port = PYCAR_MQTT_CONFIG.get("port", 8883)
            username = PYCAR_MQTT_CONFIG.get("username")
            password = PYCAR_MQTT_CONFIG.get("password")
            client_id = PYCAR_MQTT_CONFIG.get("client_id", "MCP_pyCar_Controller")
            use_ssl = PYCAR_MQTT_CONFIG.get("use_ssl", True)
            
            if not all([broker, username, password]):
                raise PyCarControlError("pyCar MQTT配置不完整：缺少broker、username或password")
            
            # 创建MQTT客户端
            self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, client_id)
            self.client.username_pw_set(username, password)
            
            # 设置回调函数
            self.client.on_connect = self._on_connect
            self.client.on_disconnect = self._on_disconnect
            self.client.on_publish = self._on_publish
            
            # 如果使用SSL
            if use_ssl:
                self.client.tls_set(ca_certs=None, certfile=None, keyfile=None,
                                cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS,
                                ciphers=None)
            
            # 连接到服务器
            logger.info(f"正在连接到pyCar MQTT服务器: {broker}:{port}")
            self.client.connect(broker, port, 60)
            self.client.loop_start()
            
            # 等待连接建立
            timeout = 10  # 10秒超时
            start_time = time.time()
            while not self.connected and (time.time() - start_time) < timeout:
                time.sleep(0.1)
            
            if not self.connected:
                raise PyCarControlError("连接pyCar MQTT服务器超时")
            
            return True
            
        except Exception as e:
            self.last_error = str(e)
            logger.error(f"连接pyCar MQTT失败: {e}")
            return False
    
    def _on_connect(self, client, userdata, flags, rc):
        """MQTT连接回调"""
        if rc == 0:
            self.connected = True
            logger.info("pyCar MQTT连接成功")
        else:
            self.connected = False
            self.last_error = f"连接失败，返回码: {rc}"
            logger.error(f"pyCar MQTT连接失败，返回码: {rc}")
    
    def _on_disconnect(self, client, userdata, rc):
        """MQTT断开回调"""
        self.connected = False
        logger.info("pyCar MQTT连接断开")
    
    def _on_publish(self, client, userdata, mid):
        """MQTT发布回调"""
        logger.debug(f"消息发布成功，消息ID: {mid}")
    
    def send_command(self, command):
        """发送控制指令到pyCar"""
        if not self.connected:
            raise PyCarControlError("未连接到pyCar MQTT服务器")
        
        topic = PYCAR_MQTT_CONFIG.get("topic", "web_robot/command")
        
        try:
            result = self.client.publish(topic, command)
            if result.rc == mqtt.MQTT_ERR_SUCCESS:
                logger.info(f"成功发送指令到pyCar: {command}")
                return True
            else:
                raise PyCarControlError(f"发送指令失败，错误码: {result.rc}")
        except Exception as e:
            raise PyCarControlError(f"发送指令时出错: {e}")
    
    def disconnect(self):
        """断开MQTT连接"""
        if self.client:
            self.client.loop_stop()
            self.client.disconnect()
            self.connected = False

# 全局MQTT客户端实例
pycar_mqtt_client = PyCarMQTTClient()

@mcp.tool()
def control_pycar(action: str, duration: float = 1.0) -> dict:
    """
    控制pyCar智能小车。支持移动控制、灯光控制和特殊功能。
    
    参数:
    - action: 控制指令，支持以下命令：
    移动控制: forward/前进/w/1, backward/后退/s/2, left/左转/a/3, right/右转/d/4, stop/停止/x/0
    灯光控制: light_toggle/L (所有灯切换), tail_lights/L (尾灯切换), blink/B (闪烁5次)
    特殊功能: calibrate/cal (速度校准), rotate_right/RR (顺时针旋转), rotate_left/RL (逆时针旋转)
    - duration: 动作持续时间（秒），仅对某些动作有效，默认1.0秒
    
    返回: 包含操作结果的字典
    """
    try:
        # 验证配置
        if not PYCAR_MQTT_CONFIG:
            raise PyCarControlError("pyCar MQTT配置未在config.json中设置")
        
        # 如果未连接，尝试连接
        if not pycar_mqtt_client.connected:
            logger.info("pyCar MQTT未连接，尝试连接...")
            if not pycar_mqtt_client.connect():
                raise PyCarControlError(f"无法连接到pyCar: {pycar_mqtt_client.last_error}")
        
        # 标准化指令
        command = action.lower().strip()
        
        # 指令映射
        command_map = {
            # 移动控制
            "forward": "forward", "前进": "forward", "w": "w", "1": "1",
            "backward": "backward", "后退": "backward", "s": "s", "2": "2", 
            "left": "left", "左转": "left", "a": "a", "3": "3",
            "right": "right", "右转": "right", "d": "d", "4": "4",
            "stop": "stop", "停止": "stop", "x": "x", "0": "0",
            
            # 灯光控制
            "light_toggle": "L", "l": "L", "lights": "L",
            "tail_lights": "L", "l": "L", "tail": "L",
            "blink": "B", "b": "B", "flash": "B",
            
            # 特殊功能
            "calibrate": "cal", "cal": "cal", "校准": "cal",
            "rotate_right": "RR", "rr": "RR", "顺时针": "RR", "右旋": "RR",
            "rotate_left": "RL", "rl": "RL", "逆时针": "RL", "左旋": "RL"
        }
        
        # 获取实际发送的指令
        mqtt_command = command_map.get(command, command)
        
        # 发送指令
        pycar_mqtt_client.send_command(mqtt_command)
        
        # 生成结果描述
        action_descriptions = {
            "forward": f"小车前进20厘米",
            "w": f"小车前进20厘米", 
            "1": f"小车前进20厘米",
            "backward": f"小车后退1秒",
            "s": f"小车后退1秒",
            "2": f"小车后退1秒",
            "left": f"小车左转0.5秒",
            "a": f"小车左转0.5秒",
            "3": f"小车左转0.5秒", 
            "right": f"小车右转0.5秒",
            "d": f"小车右转0.5秒",
            "4": f"小车右转0.5秒",
            "stop": "小车停止",
            "x": "小车停止",
            "0": "小车停止",
            "L": "切换所有灯光（车头灯+尾灯）",
            "TL": "切换尾灯",
            "B": "所有灯闪烁5次",
            "cal": "开始速度校准程序",
            "RR": "顺时针旋转一圈",
            "RL": "逆时针旋转一圈"
        }
        
        description = action_descriptions.get(mqtt_command, f"执行自定义指令: {mqtt_command}")
        
        logger.info(f"pyCar控制成功: {action} -> {mqtt_command} ({description})")
        return {
            "success": True, 
            "result": f"✅ {description}",
            "command_sent": mqtt_command,
            "original_action": action
        }
        
    except (PyCarControlError, Exception) as e:
        logger.error(f"pyCar控制失败: {e}")
        return {"success": False, "error": str(e)}

@mcp.tool()
def pycar_batch_control(commands: list, delay: float = 1.0) -> dict:
    """
    批量控制pyCar，按顺序执行多个指令。
    
    参数:
    - commands: 指令列表，每个指令格式与control_pycar相同
    - delay: 指令间的延迟时间（秒），默认1.0秒
    
    返回: 包含批量操作结果的字典
    """
    try:
        if not commands:
            return {"success": False, "error": "指令列表不能为空"}
        
        if not PYCAR_MQTT_CONFIG:
            raise PyCarControlError("pyCar MQTT配置未在config.json中设置")
        
        # 确保连接
        if not pycar_mqtt_client.connected:
            if not pycar_mqtt_client.connect():
                raise PyCarControlError(f"无法连接到pyCar: {pycar_mqtt_client.last_error}")
        
        results = []
        success_count = 0
        
        for i, command in enumerate(commands, 1):
            try:
                # 执行单个指令
                result = control_pycar(command)
                if result.get("success"):
                    success_count += 1
                    status = "✅"
                else:
                    status = "❌"
                
                results.append({
                    "step": i,
                    "command": command,
                    "status": status,
                    "result": result.get("result", result.get("error", "未知错误"))
                })
                
                # 除了最后一个指令，都要延迟
                if i < len(commands):
                    time.sleep(delay)
                    
            except Exception as e:
                results.append({
                    "step": i,
                    "command": command,
                    "status": "❌",
                    "result": f"执行失败: {str(e)}"
                })
        
        # 生成总结
        total = len(commands)
        failed = total - success_count
        
        summary = f"批量控制完成：共{total}个指令，成功{success_count}个，失败{failed}个"
        
        logger.info(f"pyCar批量控制: {summary}")
        return {
            "success": failed == 0,
            "result": summary,
            "details": results,
            "statistics": {
                "total": total,
                "success": success_count, 
                "failed": failed
            }
        }
        
    except (PyCarControlError, Exception) as e:
        logger.error(f"pyCar批量控制失败: {e}")
        return {"success": False, "error": str(e)}

@mcp.tool()
def pycar_connection_status() -> dict:
    """
    检查与pyCar的MQTT连接状态。
    
    返回: 包含连接状态信息的字典
    """
    try:
        if not PYCAR_MQTT_CONFIG:
            return {
                "success": False,
                "connected": False,
                "error": "pyCar MQTT配置未在config.json中设置"
            }
        
        # 获取配置信息（隐藏敏感信息）
        broker = PYCAR_MQTT_CONFIG.get("broker", "未设置")
        port = PYCAR_MQTT_CONFIG.get("port", "未设置")
        topic = PYCAR_MQTT_CONFIG.get("topic", "未设置")
        use_ssl = PYCAR_MQTT_CONFIG.get("use_ssl", False)
        
        connection_info = {
            "broker": broker,
            "port": port,
            "topic": topic,
            "ssl_enabled": use_ssl,
            "connected": pycar_mqtt_client.connected
        }
        
        if pycar_mqtt_client.connected:
            status_msg = f"✅ 已连接到pyCar MQTT服务器 ({broker}:{port})"
        else:
            status_msg = f"❌ 未连接到pyCar MQTT服务器"
            if pycar_mqtt_client.last_error:
                status_msg += f" - 错误: {pycar_mqtt_client.last_error}"
        
        return {
            "success": True,
            "connected": pycar_mqtt_client.connected,
            "result": status_msg,
            "connection_info": connection_info
        }
        
    except Exception as e:
        logger.error(f"检查pyCar连接状态失败: {e}")
        return {"success": False, "error": str(e)}

# --- 未来可在此处添加更多工具 ---


# --- 启动服务器 ---
if __name__ == "__main__":
    # 这将启动一个提供上述所有工具的服务器
    mcp.run(transport="stdio") 